#include <windows.h>

namespace glf {

struct MyOverlapped : public OVERLAPPED {
	FileStreamImpl::Impl* impl;
	FileStreamImpl::Callback callback;
	void* arg;
};

struct SYSFILESTREAM_COMPLETE_CLASSNAME : public FileStreamImplBase {

	OVERLAPPED mOverlapped;
	HANDLE mHandle;

	SYSFILESTREAM_CLASSNAME(FileStreamImpl& fileStream) :
		FileStreamImplBase(fileStream),
		mHandle(0)
	{
		//dont use mFileStream here
		memset(&mOverlapped, 0, sizeof(mOverlapped));
	}
	~SYSFILESTREAM_CLASSNAME() {
	}

	bool Open(const char* filename, openflag flag) {
		bool ensureCreated	= TestFlag(flag, create);
		bool openAtEnd		= TestFlag(flag, atend);
		bool truncAtOpen	= TestFlag(flag, trunc);
		bool isRead			= TestFlag(flag, read);
		bool isWrite		= TestFlag(flag, write);
		//bool isASync		= TestFlag(flag, async);
		
		mHandle = 0;

		DWORD dwDesiredAccess = 0;
		DWORD dwShareMode = FILE_SHARE_READ;
		DWORD dwCreationDisposition = 0;
		DWORD dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED;

		if(isRead)
			dwDesiredAccess |= GENERIC_READ;
		if(isWrite)
			dwDesiredAccess |= GENERIC_WRITE;

		if(ensureCreated)
			dwCreationDisposition |= OPEN_ALWAYS;
		else
			dwCreationDisposition |= OPEN_EXISTING;
		
		if(truncAtOpen) {
			DeleteFileA(filename);
		}

		bool test = false;
		if(test) {
			char buffer[2048];
			GetCurrentDirectory(sizeof(buffer), buffer);
			printf("cwd %s\n", buffer);
		}

		mHandle = CreateFileA(filename, dwDesiredAccess, dwShareMode, 0, dwCreationDisposition, dwFlagsAndAttributes, 0); 

		if(mHandle == INVALID_HANDLE_VALUE) {
			return false;
		}

		DWORD result = GetFileSize(mHandle, 0);
		mFileStream.mFilePosition.SetFileSize(result);

		Seek(0, openAtEnd ? end : beg);

		return true;
	}

	void Close() {
		if(mHandle) {
			CloseHandle(mHandle);
			mHandle = 0;
		}

		//IOStream::Close();
	}

	size_t GetSize() {
		return mFileStream.GetSize();
	}

	size_t ReadWithOffset(void* buffer, size_t size, size_t offset) {
		size_t remaining = GetSize() - offset;
		
		size = Min(remaining, size);
		
		DWORD numberOfBytesRead;
		OVERLAPPED overlapped = {0};
		overlapped.Offset = offset;
		BOOL result = ReadFile(mHandle, buffer, size, &numberOfBytesRead, &overlapped);

		if(result == TRUE || ::GetLastError() == ERROR_IO_PENDING) {
			//wait for result
			if(GetOverlappedResult(mHandle, &overlapped, &numberOfBytesRead, true) == TRUE) {
				return numberOfBytesRead;
			}
		}

		return -1;
	}

	size_t Read(void* buffer, size_t length)  {
		size_t remaining = GetSize() - Tell();
		
		length = Min(remaining, length);
		
		DWORD numberOfBytesRead;
		mOverlapped.hEvent = 0;
		BOOL result = ReadFile(mHandle, buffer, length, &numberOfBytesRead, &mOverlapped);

		if(result == TRUE || ::GetLastError() == ERROR_IO_PENDING) {
			//wait for result
			if(GetOverlappedResult(mHandle, &mOverlapped, &numberOfBytesRead, true) == TRUE) { 
				mOverlapped.Offset += numberOfBytesRead;
				return numberOfBytesRead;
			}
		}

		return -1;
	}
	
	size_t Write(const void* buffer, size_t size) {
		DWORD numBytesWritten;
		mOverlapped.hEvent = 0;
		BOOL result = WriteFile(mHandle, buffer, size, &numBytesWritten, &mOverlapped);
		
		if(result == TRUE || ::GetLastError() == ERROR_IO_PENDING) {
			//wait for result
			if(GetOverlappedResult(mHandle, &mOverlapped, &numBytesWritten, true) == TRUE) { 
				mOverlapped.Offset += numBytesWritten;
				return numBytesWritten;
			}
		}

		return -1;
	}

	bool Seek(int offset, seek origin) {
		int seekOriginMapping[3] = {
			FILE_BEGIN, FILE_CURRENT, FILE_END
		};

		int fileSize = (int)GetSize();
		
		int newOffset = mOverlapped.Offset;
		
		if(origin == beg) {
			newOffset = offset;
		} else if(origin == cur) {
			newOffset += offset;
		} else if(origin == end) {
			newOffset = fileSize + offset;
		}

		if(newOffset < 0) {
			//set error?
			newOffset = 0;
		}

		mOverlapped.Offset = newOffset;

		return true;
	}

	size_t Tell() const {
		return mOverlapped.Offset;
	}
};

} //namespace glf
